#include "components/shell.h"
#include "ff.h"
#include "fs_init_fatfs.h"
#include "sys_init.h"
#include "sys_log.h"

#define RESAULT_TO_STR(r) resault_to_str_map[r]
static char *const resault_to_str_map[] =
    {
        "FR_OK",                  /* (0) Succeeded */
        "FR_DISK_ERR",            /* (1) A hard error occurred in the low level disk I/O layer */
        "FR_INT_ERR",             /* (2) Assertion failed */
        "FR_NOT_READY",           /* (3) The physical drive cannot work */
        "FR_NO_FILE",             /* (4) Could not find the file */
        "FR_NO_PATH",             /* (5) Could not find the path */
        "FR_INVALID_NAME",        /* (6) The path name format is invalid */
        "FR_DENIED",              /* (7) Access denied due to prohibited access or directory full */
        "FR_EXIST",               /* (8) Access denied due to prohibited access */
        "FR_INVALID_OBJECT",      /* (9) The file/directory object is invalid */
        "FR_WRITE_PROTECTED",     /* (10) The physical drive is write protected */
        "FR_INVALID_DRIVE",       /* (11) The logical drive number is invalid */
        "FR_NOT_ENABLED",         /* (12) The volume has no work area */
        "FR_NO_FILESYSTEM",       /* (13) There is no valid FAT volume */
        "FR_MKFS_ABORTED",        /* (14) The f_mkfs() aborted due to any problem */
        "FR_TIMEOUT",             /* (15) Could not get a grant to access the volume within defined period */
        "FR_LOCKED",              /* (16) The operation is rejected according to the file sharing policy */
        "FR_NOT_ENOUGH_CORE",     /* (17) LFN working buffer could not be allocated */
        "FR_TOO_MANY_OPEN_FILES", /* (18) Number of open files > FF_FS_LOCK */
        "FR_INVALID_PARAMETER"    /* (19) Given parameter is invalid */
};

#define HELP_MOUNT ("Mount a logical drive. Usage: mount <drive>")
#define HELP_UNMOUNT ("Unmount a logical drive. Usage: unmount <drive>")
#define HELP_DF ("Show Disk Info. Usage: df [path]")
#define HELP_LS ("List information about the FILEs. Usage: ls [path]")
#define HELP_CD ("Change the shell working directory. Usage: cd <path>")
#define HELP_CAT ("Read FILE. Usage: cat <file> <addr> <size>")
#define HELP_DUMP ("Read FILE and hex dump. Usage: dump <path> <addr> <size>")
#define HELP_RM ("Delete FILE. Usage: rm [-r:dir] <path>")
#define HELP_MV ("Move FILE. Usage: mv <from:path> <to:path>")
#define HELP_MKDIR ("Make DIR. Usage: mkdir <path>")
#define HELP_TOUCH ("Create empty FILE. Usage: touch <file>")
#define HELP_WRITE ("Write string to FILE. Usage: wr <string> (>|>>) <file>")
#define HELP_PWD ("Show current directory")
#define HELP_SYNC ("Flush cached data of the writing file")

SHELL_CMD_FN(_cmd_mount);
SHELL_CMD_FN(_cmd_unmount);

SHELL_CMD_FN(_cmd_ls);
SHELL_CMD_FN(_cmd_cd);
SHELL_CMD_FN(_cmd_cat);
SHELL_CMD_FN(_cmd_dump);
SHELL_CMD_FN(_cmd_write);
SHELL_CMD_FN(_cmd_rm);
SHELL_CMD_FN(_cmd_mv);
SHELL_CMD_FN(_cmd_mkdir);
SHELL_CMD_FN(_cmd_touch);
SHELL_CMD_FN(_cmd_df);
SHELL_CMD_FN(_cmd_pwd);
SHELL_CMD_FN(_cmd_sync);

SHELL_CMD_CP_FN(_cmd_mount_cp);
SHELL_CMD_CP_FN(_cmd_unmount_cp);

SHELL_CMD_CP_FN(_cmd_ls_cp);
SHELL_CMD_CP_FN(_cmd_cd_cp);
SHELL_CMD_CP_FN(_cmd_cat_cp);
SHELL_CMD_CP_FN(_cmd_dump_cp);
SHELL_CMD_CP_FN(_cmd_write_cp);
SHELL_CMD_CP_FN(_cmd_rm_cp); // 比较完整的参考结构
SHELL_CMD_CP_FN(_cmd_mv_cp);
SHELL_CMD_CP_FN(_cmd_mkdir_cp);
SHELL_CMD_CP_FN(_cmd_touch_cp);
SHELL_CMD_CP_FN(_cmd_df_cp);

SHELL_REGISTER_CMD(
    register_file_system_mount,
    SH_SETUP_CMD("mount",   HELP_MOUNT,   _cmd_mount,   _cmd_mount_cp),
    SH_SETUP_CMD("unmount", HELP_UNMOUNT, _cmd_unmount, _cmd_unmount_cp),
);

SHELL_REGISTER_CMD(
    register_file_system_command,
    SH_SETUP_CMD("ls",    HELP_LS,    _cmd_ls,    _cmd_ls_cp),
    SH_SETUP_CMD("cd",    HELP_CD,    _cmd_cd,    _cmd_cd_cp),
    SH_SETUP_CMD("cat",   HELP_CAT,   _cmd_cat,   _cmd_cat_cp),
    SH_SETUP_CMD("dump",  HELP_DUMP,  _cmd_dump,  _cmd_dump_cp),
    SH_SETUP_CMD("wr",    HELP_WRITE, _cmd_write, _cmd_write_cp),
    SH_SETUP_CMD("rm",    HELP_RM,    _cmd_rm,    _cmd_rm_cp),
    SH_SETUP_CMD("mv",    HELP_MV,    _cmd_mv,    _cmd_mv_cp),
    SH_SETUP_CMD("mkdir", HELP_MKDIR, _cmd_mkdir, _cmd_mkdir_cp),
    SH_SETUP_CMD("touch", HELP_TOUCH, _cmd_touch, _cmd_touch_cp),
    SH_SETUP_CMD("df",    HELP_DF,    _cmd_df,    _cmd_df_cp),
    SH_SETUP_CMD("pwd",   HELP_PWD,   _cmd_pwd,   NULL),
    SH_SETUP_CMD("sync",  HELP_SYNC,  _cmd_sync,  NULL),
);

typedef enum
{
    _SCAN_TYPE_DIR,
    _SCAN_TYPE_FILE,
} scan_type_t;

static void _completion_path(sh_t *sh_hdl, int argc, const char *argv[], bool flag, scan_type_t type);
static void _split_path(const TCHAR **path, const char **arg_str, char buf[CONFIG_SH_MAX_LINE_LEN], const char *argv);
static FRESULT _scan_files(sh_t *sh_hdl,
                           const TCHAR *path,
                           scan_type_t type,
                           const char *arg_str);
static FRESULT _rm_dir(char *path);
static FRESULT _df(const char *path, DWORD *totle_byte, DWORD *free_byte);
static void _cmd_df_print_df(sh_t *sh_hdl, const char *volume);

static void _completion_path(sh_t *sh_hdl, int argc, const char *argv[], bool flag, scan_type_t type)
{
    if (argc == 0 || flag)
    {
        _scan_files(sh_hdl, "./", type, "");
    }
    else
    {
        char buf[CONFIG_SH_MAX_LINE_LEN];
        const TCHAR *path;
        const char *arg_str;

        _split_path(&path, &arg_str, buf, argv[argc - 1]);
        _scan_files(sh_hdl, path, type, arg_str);
    }
}

static void _split_path(const TCHAR **path, const char **arg_str, char buf[CONFIG_SH_MAX_LINE_LEN], const char *argv)
{
    strcpy(buf, argv);

    char *p = strrchr(buf, '/');
    if (p == NULL)
    {
        *path = "";
        p = buf;
    }
    else
    {
        *path = buf;
        *p = '\0';
        p = &p[1];
    }
    *arg_str = p;
}

static FRESULT _scan_files(
    sh_t *sh_hdl,
    const TCHAR *path, /* Start node to be scanned (***also used as work area***) */
    scan_type_t type,
    const char *arg_str)
{
    FRESULT res;
    DIR dir;
    FILINFO fno;
    res = f_stat(path, &fno);
    if ((res != FR_OK) || fno.fattrib & AM_DIR)
    {
        res = f_opendir(&dir, path); /* Open the directory */
        if (res == FR_OK)
        {
            if (type == _SCAN_TYPE_DIR && arg_str == NULL)
            {
                char pwd[128];
                if (f_getcwd(pwd, sizeof(pwd)) == FR_OK)
                {
                    char *p = strchr(pwd, '/');
                    if (p && p[1] != '\0')
                    {
                        shell_print(sh_hdl, "\x1B[34m%-14s\x1B[0m  ", "../");
                    }
                }
                shell_print(sh_hdl, "\x1B[34m%-14s\x1B[0m  ", "./");
            }

            for (;;)
            {
                char fname[FF_LFN_BUF + 2];

                res = f_readdir(&dir, &fno); /* Read a directory item */
                if (res != FR_OK || fno.fname[0] == 0)
                {
                    break; /* Break on error or end of dir */
                }

                if (fno.fattrib & AM_DIR) /* It is a directory */
                {
                    if (type == _SCAN_TYPE_DIR)
                    {
                        if (arg_str == NULL)
                        {
                            SYS_SNPRINT(fname, sizeof(fname), "%s/", fno.fname);
                            shell_print(sh_hdl, "\x1B[34m%-14s\x1B[0m  ", fname);
                        }
                        else
                        {
                            SYS_SNPRINT(fname, sizeof(fname), "%s/", fno.fname);
                            sh_completion_resource(sh_hdl, arg_str, fname, NULL);
                        }
                    }
                }
                else /* It is a file. */
                {
                    if (type == _SCAN_TYPE_FILE)
                    {
                        if (arg_str == NULL)
                        {
                            shell_print(sh_hdl, "%-16s  ", fno.fname);
                        }
                        else
                        {
                            SYS_SNPRINT(fname, sizeof(fname), "%s ", fno.fname);
                            sh_completion_resource(sh_hdl, arg_str, fname, NULL);
                        }
                    }
                }
            }
            f_closedir(&dir);
        }
        else
        {
            if (arg_str == NULL)
            {
                shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
            }
        }
    }
    else
    {
        if (arg_str == NULL)
        {
            shell_print(sh_hdl, "%s  ", fno.fname);
        }
    }
    return res;
}

static FRESULT _rm_dir(char *path)
{
    FRESULT res;
    DIR dir;
    FILINFO fno;
    res = f_stat(path, &fno);
    if (res != FR_OK)
    {
        return res;
    }
    if (fno.fattrib & AM_DIR)
    {
        res = f_opendir(&dir, path); /* Open the directory */
        if (res == FR_OK)
        {
            while (1)
            {
                res = f_readdir(&dir, &fno); /* Read a directory item */
                if (res != FR_OK || fno.fname[0] == 0)
                {
                    break; /* Break on error or end of dir */
                }
                if (fno.fattrib & AM_DIR) /* It is a directory */
                {
                    UINT i = strlen(path);
                    SYS_SPRINT(&path[i], "/%s", fno.fname);
                    res = _rm_dir(path); /* Enter the directory */
                    if (res != FR_OK)
                    {
                        break;
                    }
                    path[i] = 0;
                    f_unlink(fno.fname);
                }
                else
                {
                    UINT i = strlen(path);
                    SYS_SPRINT(&path[i], "/%s", fno.fname);
                    f_unlink(path);
                    path[i] = 0;
                }
            }
            f_closedir(&dir);
        }
        f_unlink(path);
    }
    else
    {
        f_unlink(path);
    }
    return res;
}

static FRESULT _df(const char *path, DWORD *totle_byte, DWORD *free_byte)
{
    FATFS *fs;
    DWORD fre_clust, fre_sect, tot_sect;
    FRESULT res;
    /* Get volume information and free clusters of drive 1 */
    res = f_getfree(path, &fre_clust, &fs);
    if (res == FR_OK)
    {
        /* Get total sectors and free sectors */
        tot_sect = (fs->n_fatent - 2) * fs->csize;
        fre_sect = fre_clust * fs->csize;
#if FF_MAX_SS != FF_MIN_SS
        *totle_byte = tot_sect * fs->ssize;
        *free_byte = fre_sect * fs->ssize;
#else
        *totle_byte = tot_sect * FF_MAX_SS;
        *free_byte = fre_sect * FF_MAX_SS;
#endif
    }
    return res;
}

static void _cmd_df_print_df(sh_t *sh_hdl, const char *volume)
{
    if (volume && *volume != '\0')
    {
        DWORD fre_bytes, tot_bytes;
        FRESULT res = _df(volume, &tot_bytes, &fre_bytes);
        if (res != FR_OK)
        {
            shell_print(sh_hdl, "%s Failure: %s\r\n", volume, RESAULT_TO_STR(res));
        }
        else
        {
            shell_print(sh_hdl, "%s %lu/%lu KiB.\r\n", volume, fre_bytes / 1024, tot_bytes / 1024);
        }
    }
}

SHELL_CMD_FN(_cmd_df)
{
    if (argc == 0)
    {
        for (int i = 0; i < FF_VOLUMES; i++)
        {
            _cmd_df_print_df(sh_hdl, f_disk_get_volume(i));
        }
    }
    else
    {
        _cmd_df_print_df(sh_hdl, argc[argv - 1]);
    }
    return 0;
}

SHELL_CMD_FN(_cmd_pwd)
{
    char prompt[128];
    FRESULT res = f_getcwd(prompt, sizeof(prompt));
    if (res != FR_OK)
    {
        shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
        return -1;
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", prompt);
        return 0;
    }
}

SHELL_CMD_CP_FN(_cmd_df_cp)
{
    if (argc + flag < 2)
    {
        for (int i = 0; i < FF_VOLUMES; i++)
        {
            const TCHAR *volume = f_disk_get_volume(i);
            if (volume && *volume != '\0')
            {
                sh_completion_resource(sh_hdl, NULL, volume, NULL);
            }
        }
    }
}

SHELL_CMD_FN(_cmd_mount)
{
    if (argc)
    {
        /* 整理驱动器名为 disk:/ff:argv[0] */
        char name[CONFIG_MAX_VOL_NAME_LEN] = "disk:/ff:";
        strncat(name, argv[0], CONFIG_MAX_VOL_NAME_LEN - 1);

        /* 绑定驱动器名对应的对象 */
        device_disk_fatfs_t dev = device_disk_fatfs_binding(name);
        if (dev == NULL)
        {
            shell_print(sh_hdl, "Failure: device '%s' not found\r\n", argv[0]);
            return -1;
        }

        if (f_disk_get_id(dev) >= 0)
        {
            shell_print(sh_hdl, "Failure: device '%s' already mounted\r\n", argv[0]);
            return -1;
        }

        /* 注册驱动器 */
        if (f_disk_regist(dev, argv[0], -1) != 0)
        {
            shell_print(sh_hdl, "%s fs regist ERROR!\r\n", argv[0]);
            return -1;
        }

        /* 挂载 */
        FRESULT res = f_mount(&dev->data->fs, dev->data->volume_str, 1);
        if (res == FR_NO_FILESYSTEM)
        {
            BYTE work[FF_MAX_SS];
            res = f_mkfs(dev->data->volume_str, 0, work, sizeof(work));
            res = f_mount(NULL, dev->data->volume_str, 0);           // 取消文件系统
            res = f_mount(&dev->data->fs, dev->data->volume_str, 1); // 挂载文件系统
        }
        if (res != FR_OK)
        {
            f_disk_unregist(argv[0]);
            SYS_LOG_WRN("%s fs init ERROR!\r\n", dev->data->volume_str);
            return -1;
        }

        /* 测试命令 "ls" 是否可用，如果未可用则动态注册命令组 */
        const char *argv[] = {
            "ls",
        };
        sh_cmd_info_t info = sh_cmd_test(sh_hdl, 1, argv);
        if (info.cmd_count == 0)
        {
            sh_register_cmd(&register_file_system_command);
        }
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_MOUNT);
        return -1;
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_mount_cp)
{
    if (argc + flag < 2)
    {
        int num = 0;
        while (1)
        {
            const char *name = "disk:/ff:";
            const char *disk = device_disk_fatfs_search(name, num++);
            if (disk == NULL)
            {
                break;
            }

            device_disk_fatfs_t dev = device_disk_fatfs_binding(disk);
            if (dev == NULL)
            {
                break;
            }

            if (f_disk_get_id(dev) < 0)
            {
                sh_completion_resource(sh_hdl, NULL, &disk[strlen(name)], NULL);
            }
        }
    }
}

SHELL_CMD_FN(_cmd_unmount)
{
    if (argc)
    {
        int id = f_disk_get_id_by_name(argv[0]);
        if (id < 0)
        {
            shell_print(sh_hdl, "%s fs unregist ERROR!\r\n", argv[0]);
            return 0;
        }
        const TCHAR *VolumeStr = f_disk_get_volume(id);
        if (VolumeStr == NULL)
        {
            shell_print(sh_hdl, "%s fs unregist ERROR!\r\n", argv[0]);
            return 0;
        }

        FRESULT res = f_mount(NULL, VolumeStr, 0);
        if (res != FR_OK)
        {
            shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
            return -1;
        }

        /* 取消注册驱动器 */
        f_disk_unregist(argv[0]);

        /* 计算有多少个已挂载的驱动器 */
        int volumes = 0;
        for (int i = 0; i < FF_VOLUMES; i++)
        {
            if (f_disk_get_dev(i) != NULL)
            {
                volumes++;
            }
        }

        if (volumes == 0)
        {
            /* 测试命令 "ls" 是否可用，如果可用则取消注册命令组 */
            const char *argv[] = {
                "ls",
            };
            sh_cmd_info_t info = sh_cmd_test(sh_hdl, 1, argv);
            if (info.cmd_count != 0)
            {
                sh_unregister_cmd(&register_file_system_command);
            }
        }
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_MOUNT);
        return -1;
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_unmount_cp)
{
    if (argc + flag < 2)
    {
        for (int i = 0; i < FF_VOLUMES; i++)
        {
            const TCHAR *volume = f_disk_get_volume(i);
            if (volume != NULL)
            {
                sh_completion_resource(sh_hdl, NULL, volume, NULL);
            }
        }
    }
}

SHELL_CMD_FN(_cmd_ls)
{
    if (argc == 0)
    {
        if (_scan_files(sh_hdl, "./", _SCAN_TYPE_DIR, NULL) == FR_OK &&
            _scan_files(sh_hdl, "./", _SCAN_TYPE_FILE, NULL) == FR_OK)
        {
            shell_print(sh_hdl, "\r\n");
        }
    }
    else
    {
        if (_scan_files(sh_hdl, argv[0], _SCAN_TYPE_DIR, NULL) == FR_OK &&
            _scan_files(sh_hdl, argv[0], _SCAN_TYPE_FILE, NULL) == FR_OK)
        {
            shell_print(sh_hdl, "\r\n");
        }
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_ls_cp)
{
    if (argc + flag < 2)
    {
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
    }
}

SHELL_CMD_FN(_cmd_cd)
{
    if (argc)
    {
        static char s_prompt_bak[128];
        static char s_prompt[128];
        char prompt[128];
        const TCHAR *path = argv[0];
        FRESULT res;

        res = f_getcwd(prompt, sizeof(prompt));
        if (res != FR_OK)
        {
            prompt[0] = '\0';
        }

        if (!strcmp(path, "-"))
        {
            path = s_prompt_bak;
        }

        f_chdrive(path);
        res = f_chdir(path);
        if (res != FR_OK)
        {
            shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
        }
        else
        {
            f_getcwd(s_prompt, sizeof(s_prompt));

            if (prompt[0] != '\0')
            {
                strcat(s_prompt, "> ");
                shell_set_prompt(sh_hdl, s_prompt);

                strcat(prompt, "> ");
                if (strcmp(s_prompt, prompt))
                {
                    prompt[strlen(prompt) - 2] = '\0';
                    strcpy(s_prompt_bak, prompt);
                }
            }
            else
            {
                strcpy(s_prompt_bak, s_prompt);
                strcat(s_prompt, "> ");
                shell_set_prompt(sh_hdl, s_prompt);
            }
        }
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_CD);
        return -1;
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_cd_cp)
{
    if (argc + flag < 2)
    {
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
    }
}

SHELL_CMD_FN(_cmd_rm)
{
    FRESULT res;
    uint8_t dir_flag = 0;
    uint8_t param_num = 1;
    /* 查找可选参数 */
    if (strcmp(argv[0], "-r") == 0)
    {
        dir_flag = 1;
        param_num = 2;
    }

    if (argc >= param_num)
    {
        if (dir_flag)
        {
            char path[0x100];
            strncpy(path, argv[1], sizeof(path));
            res = _rm_dir(path);
            if (res != FR_OK)
            {
                shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
                return -1;
            }
        }
        else
        {
            FILINFO fno;
            res = f_stat(argv[0], &fno);
            if (res != FR_OK)
            {
                shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
                return -1;
            }
            if (fno.fattrib & AM_DIR)
            {
                shell_print(sh_hdl, "Failure: %s\r\n", "PATH is DIR");
            }
            else
            {
                res = f_unlink(argv[0]);
                if (res != FR_OK)
                {
                    shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
                    return -1;
                }
            }
        }
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_RM);
        return -1;
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_rm_cp)
{
    if (argc + flag < 3)
    {
        if (argc == 0) // 按命令补全默认值
        {
            _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_FILE);
            _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
        }
        else
        {
            if (flag) // 按参数补全默认值
            {
                if (strcmp(argv[argc - 1], "-r") == 0)
                {
                    _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
                }
            }
            else // 正在输入
            {
                if (*argv[argc - 1] == '-') // 按正在输入的参数补全
                {
                    if (argc != 1) // 参数条件过滤
                    {
                        return;
                    }

                    const sh_cp_param_t param[] = {
                        {"-r ", "Delete directory"},
                        {NULL},
                    };
                    sh_completion_param(sh_hdl, param);
                }
                else // 按正在输入的值补全
                {
                    if (argc == 1)
                    {
                        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_FILE);
                        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
                    }
                    else if (argc >= 2 && strcmp(argv[argc - 2], "-r") == 0)
                    {
                        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
                    }
                }
            }
        }
    }
}

SHELL_CMD_FN(_cmd_mv)
{
    if (argc >= 2)
    {
        FRESULT res;
        res = f_rename(argv[0], argv[1]);
        if (res != FR_OK)
        {
            shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
            return -1;
        }
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_MV);
        return -1;
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_mv_cp)
{
    if (argc + flag < 3)
    {
        if (argc == 0) // 按命令补全默认值
        {
            _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
            _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_FILE);
        }
        else
        {
            if (flag) // 按参数补全默认值
            {
                _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
            }
            else // 正在输入
            {
                if (argc == 1)
                {
                    _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
                    _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_FILE);
                }
                else
                {
                    _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
                }
            }
        }
    }
}

SHELL_CMD_FN(_cmd_sync)
{
    // f_sync(&fp);
    return 0;
}

SHELL_CMD_FN(_cmd_mkdir)
{
    if (argc >= 1)
    {
        FRESULT res;
        res = f_mkdir(argv[0]);
        if (res != FR_OK)
        {
            shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
            return 0;
        }
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_MKDIR);
        return -1;
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_mkdir_cp)
{
    if (argc + flag < 2)
    {
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
    }
}

SHELL_CMD_FN(_cmd_touch)
{
    if (argc >= 1)
    {
        FRESULT res;
        FIL fp;
        res = f_open(&fp, argv[0], FA_CREATE_NEW);
        if ((res != FR_OK) && (res != FR_EXIST))
        {
            shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
            return -1;
        }
        f_close(&fp);
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_TOUCH);
        return -1;
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_touch_cp)
{
    if (argc + flag < 2)
    {
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
    }
}

SHELL_CMD_FN(_cmd_cat)
{
    if (argc >= 1)
    {
        FRESULT res;
        FIL fp;
        res = f_open(&fp, argv[0], FA_READ);
        if (res != FR_OK)
        {
            shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
            return -1;
        }
        else
        {
            char buf[128];
            UINT br;
            while (1)
            {
                res = f_read(&fp, buf, 128, &br);
                if (res != FR_OK)
                {
                    shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
                    f_close(&fp);
                    return -1;
                }
                if (br == 0)
                {
                    break;
                }
                for (UINT i = 0; i < br; i++)
                {
                    shell_print(sh_hdl, "%c", buf[i]);
                }
            }
        }
        f_close(&fp);
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_CAT);
        return -1;
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_cat_cp)
{
    if (argc + flag < 2)
    {
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_FILE);
    }
}

SHELL_CMD_FN(_cmd_dump)
{
    if (argc >= 3)
    {
        FIL fp;
        char buf[0x100];
        sh_parse_t value;
        int addr = 0;
        int size = 0;
        FRESULT res;

        value = sh_parse_value(argv[1]);
        if (value.type != _PARSE_TYPE_UNSIGNED)
        {
            shell_print(sh_hdl, "Error: Param 2: '%s' is not an unsigned integer\r\n", argv[1]);
            return -1;
        }
        addr = value.value.val_unsigned;

        value = sh_parse_value(argv[2]);
        if (value.type != _PARSE_TYPE_UNSIGNED)
        {
            shell_print(sh_hdl, "Error: Param 3: '%s' is not an unsigned integer\r\n", argv[2]);
            return -1;
        }
        size = value.value.val_unsigned;

        if (size > sizeof(buf))
        {
            shell_print(sh_hdl, "Error: Param 3: size = %d > %d\r\n", size, sizeof(buf));
            return -1;
        }

        res = f_open(&fp, argv[0], FA_READ);
        if (res != FR_OK)
        {
            shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
            return -1;
        }

        res = f_lseek(&fp, addr);
        if (res != FR_OK)
        {
            shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
            f_close(&fp);
            return -1;
        }

        UINT br;
        res = f_read(&fp, buf, size, &br);
        if (res != FR_OK)
        {
            shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
            f_close(&fp);
            return -1;
        }
        _sys_log_dump(addr, buf, br, 1, 0);

        f_close(&fp);
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_DUMP);
        return -1;
    }
    return 0;
}

SHELL_CMD_CP_FN(_cmd_dump_cp)
{
    if (argc + flag < 2)
    {
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_FILE);
    }
}

SHELL_CMD_FN(_cmd_write)
{
    if (argc >= 3)
    {
        FIL fp;
        int op = 0;

        if (!strcmp(argv[1], ">"))
        {
            op = FA_CREATE_ALWAYS;
        }
        else if (!strcmp(argv[1], ">>"))
        {
            op = FA_OPEN_EXISTING;
        }
        else
        {
            shell_print(sh_hdl, "%s\r\n", HELP_WRITE);
            return -1;
        }

        FRESULT res = f_open(&fp, argv[2], FA_WRITE | op);
        if (res != FR_OK)
        {
            if (op == FA_OPEN_EXISTING)
            {
                res = f_open(&fp, argv[2], FA_WRITE | FA_CREATE_ALWAYS);
            }
            if (res != FR_OK)
            {
                shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
                return -1;
            }
        }

        do
        {
            if (op == FA_CREATE_ALWAYS)
            {
                res = f_lseek(&fp, 0);
            }
            else
            {
                res = f_lseek(&fp, f_size(&fp));
            }

            if (res != FR_OK)
            {
                shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
                break;
            }

            UINT br;
            res = res != FR_OK ? res : f_write(&fp, argv[0], strlen(argv[0]), &br);
            res = res != FR_OK ? res : f_write(&fp, "\r\n", 2, &br);
            if (res != FR_OK)
            {
                shell_print(sh_hdl, "Failure: %s\r\n", RESAULT_TO_STR(res));
                break;
            }

        } while (0);

        f_close(&fp);

        return -(res != FR_OK);
    }
    else
    {
        shell_print(sh_hdl, "%s\r\n", HELP_WRITE);
        return -1;
    }
}

SHELL_CMD_CP_FN(_cmd_write_cp)
{
    if (argc + flag < 4)
    {
        if (argc == 0) // 按命令补全默认值
        {
            return;
        }
        else
        {
            if (flag) // 按参数补全默认值
            {
                if (argc == 1)
                {
                    const sh_cp_param_t param[] = {
                        {">", "Output redirection"},
                        {">>", "Output append redirection"},
                        {NULL},
                    };
                    sh_completion_param(sh_hdl, param);
                }
                else if (argc == 2)
                {
                    _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_FILE);
                    _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
                }
            }
            else // 正在输入
            {
                if (*argv[argc - 1] == '>') // 按正在输入的参数补全
                {
                    if (argc != 2) // 参数条件过滤
                    {
                        return;
                    }

                    const sh_cp_param_t param[] = {
                        {">", "Output redirection"},
                        {">>", "Output append redirection"},
                        {NULL},
                    };
                    sh_completion_param(sh_hdl, param);
                }
                else // 按正在输入的值补全
                {
                    if (argc > 2 && (!strcmp(argv[argc - 2], ">") || !strcmp(argv[argc - 2], ">>")))
                    {
                        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_FILE);
                        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
                    }
                }
            }
        }
    }
}
